import cv2
import tkinter as tk
from tkinter import Scale, Button,Label
from PIL import Image, ImageTk
import threading
import queue
import time
import requests
import uuid
import os
import numpy as np
import base64
class CameraApp:
    # 目标URL
    url = 'http://192.168.1.99:5000/process_image_verifyApp'

    def determine_qr_points_positions(self,qr_points):
        # 初始化点的坐标，使用极端值开始比较
        left_most = {'x': float('inf'), 'point': None}
        right_most = {'x': float('-inf'), 'point': None}
        top_most = {'y': float('inf'), 'point': None}
        bottom_most = {'y': float('-inf'), 'point': None}

        # 遍历qr_points以确定最左、最右、最上、最下的点
        for point in qr_points:
            # 更新最左侧的点
            if point[0] < left_most['x']:
                left_most['x'] = point[0]
                left_most['point'] = point
            # 更新最右侧的点
            if point[0] > right_most['x']:
                right_most['x'] = point[0]
                right_most['point'] = point
            # 更新最上侧的点
            if point[1] < top_most['y']:
                top_most['y'] = point[1]
                top_most['point'] = point
            # 更新最下侧的点
            if point[1] > bottom_most['y']:
                bottom_most['y'] = point[1]
                bottom_most['point'] = point

        return {
            'left': left_most['point'],
            'right': right_most['point'],
            'top': top_most['point'],
            'bottom': bottom_most['point']
        }

    def calculate_angle_and_direction(self, point1, point2, point3, point4):
        # 将线段转换为向量
        vector1 = np.array([point2[0] - point1[0], point2[1] - point1[1]])
        vector2 = np.array([point4[0] - point3[0], point4[1] - point3[1]])

        # 计算两个向量的点积和模
        dot_product = np.dot(vector1, vector2)
        norm1 = np.linalg.norm(vector1)
        norm2 = np.linalg.norm(vector2)

        # 使用点积公式计算角度
        cos_angle = dot_product / (norm1 * norm2)
        angle = np.arccos(cos_angle)

        # 计算弧度转换为度
        angle_degrees = np.degrees(angle)

        # 判断旋转方向（叉积）
        cross_product = np.cross(vector1, vector2)
        direction = "逆时针" if cross_product > 0 else "顺时针"

        return angle_degrees, direction

    def __init__(self, window, window_title):
        self.window = window
        self.window.title(window_title)

        # 创建 OpenCV 摄像头对象
        self.cap = cv2.VideoCapture(0)  # 0 表示默认摄像头
        self.cap.set(cv2.CAP_PROP_FPS, 60)  # 设置帧率为60

        #存储数据文件的路径（初始化为None）
        self.last_saved_data_path = None

        self.processed_frame = None  # 存储处理后的最新一帧图像

        # 初始化 qr_points 和 info
        self.qr_points = None
        self.qr_info = None
        self.x_offset_sj = 0  # 初始化x偏移实际值
        self.y_offset_sj = 0  # 初始化y偏移实际值

        # 初始化坐标点，可能需要根据实际情况调整初始值
        self.qr_center_point = (0, 0)
        self.point1 = (0, 0)
        self.box_center_points = (0, 0)
        self.point2 = (0, 0)
        self.capture = None
        # 获取摄像头的默认分辨率和参数
        self.default_width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        self.default_height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
        self.default_exposure = int(self.cap.get(cv2.CAP_PROP_EXPOSURE))
        self.default_fps = int(self.cap.get(cv2.CAP_PROP_FPS))

        # 假设 self.default_width 和 self.default_height 是您想要使用的尺寸
        # 设置标签的尺寸相同



        # 创建主框架来分隔界面为左、中、右三部分
        main_frame = tk.Frame(window)
        main_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 左侧框架设置
        left_frame = tk.Frame(main_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10)

        # # 中间框架设置 - 展示摄像头实时画面
        # camera_frame = tk.Frame(main_frame)
        # camera_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        #
        # self.label = tk.Label(camera_frame)
        # self.label.pack(fill=tk.BOTH, expand=True)
        # self.label.pack(padx=10, pady=10)

        # 中间框架设置 - 展示摄像头实时画面和拍摄按钮
        self.camera_frame = tk.Frame(main_frame)
        self.camera_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 摄像头实时画面
        self.label = tk.Label(self.camera_frame)
        self.label.pack(fill=tk.BOTH, expand=True)

        # 在摄像头画面下方添加拍摄按钮的容器
        self.capture_button_frame = tk.Frame(self.camera_frame)
        self.capture_button_frame.pack(fill=tk.X, padx=10, pady=2)

        # 在容器内添加一个固定大小的 Label 以强制设置容器大小
        self.dummy_label = tk.Label(self.capture_button_frame, width=55, height=5)
        self.dummy_label.pack(pady=(1, 5))
        # 拍摄按钮
        self.btn_capture = tk.Button(self.capture_button_frame, text="拍摄", command=self.capture_image)
        self.btn_capture.pack(side=tk.BOTTOM, fill=tk.X)

        # 初始时隐藏拍摄按钮的容器
        self.capture_button_frame.pack_forget()

        # 右侧框架设置 - 展示拍摄画面、检测二维码按钮和文本框
        right_frame = tk.Frame(main_frame)
        right_frame.pack(side=tk.LEFT, fill=tk.Y, padx=10)


        self.captured_photo_label = tk.Label(right_frame)
        self.captured_photo_label.pack(pady=10)  # 添加一点垂直空间
        # 在右侧框架内创建一个新框架用于放置检测二维码按钮和文本框
        qr_control_frame = tk.Frame(right_frame)
        qr_control_frame.pack(fill=tk.X, pady=5)  # 确保横向填充，以及与其他控件有适当的间距

        # 创建按钮：检测二维码
        self.btn_refresh_qr_info = Button(qr_control_frame, text="检测二维码", command=self.refresh_qr_code_qr_info)
        self.btn_refresh_qr_info.pack(side=tk.LEFT, padx=5)
        self.btn_refresh_qr_info.pack_forget()  # 初始时隐藏按钮

        # 创建保存图片按钮
        self.btn_save_image = Button(qr_control_frame, text="保存图片", command=self.save_image)
        self.btn_save_image.pack(side=tk.LEFT, padx=5)
        self.btn_save_image.pack_forget()  # 初始时隐藏按钮

        self.qr_info_text = tk.Text(qr_control_frame, height=5, width=55,font=("Microsoft YaHei", 10))
        self.qr_info_text.pack(pady=5)
        self.qr_info_text.pack_forget()  # 初始时隐藏文本框


        # 左侧框架内容
        # 上部 - 按钮
        top_buttons_frame = tk.Frame(left_frame)
        top_buttons_frame.pack(pady=10)
        self.btn_toggle_camera = tk.Button(top_buttons_frame, text="开/关摄像头", command=self.toggle_camera)
        self.btn_toggle_camera.pack(side=tk.TOP, fill=tk.X)

        # 使用此函数创建所有滑块，同时指定默认值
        self.exposure_scale = self.create_slider(left_frame, "曝光时间", -13, 0, self.set_exposure, -6)
        self.gain_scale = self.create_slider(left_frame, "增益", 0, 255, self.set_gain, 110)
        self.hue_scale = self.create_slider(left_frame, "色调", -2000, 2000, self.set_hue, 0)
        self.gamma_scale = self.create_slider(left_frame, "Gamma", 64, 300, self.set_gamma, 124)
        self.brightness_scale = self.create_slider(left_frame, "亮度", -64, 64, self.set_brightness, 0)
        self.contrast_scale = self.create_slider(left_frame, "对比度", 0, 95, self.set_contrast, 0)
        self.saturation_scale = self.create_slider(left_frame, "饱和度", 0, 255, self.set_saturation, 54)
        self.sharpness_scale = self.create_slider(left_frame, "锐度", 0, 255, self.set_sharpness, 0)


        self.btn_reset_defaults = tk.Button(left_frame, text="默认参数", command=self.reset_camera_defaults)
        self.btn_reset_defaults.pack(side=tk.TOP, fill=tk.X)

        # 底部 - 退出按钮
        btn_quit = tk.Button(left_frame, text="退出", command=self.quit)
        btn_quit.pack(side=tk.BOTTOM, pady=10)

        # 添加摄像头控制标志
        self.camera_enabled = False


        # 设置关闭窗口时的事件处理
        self.window.protocol("WM_DELETE_WINDOW", self.quit)

        # 创建并启动图像捕获线程
        self.frame_queue = queue.Queue(maxsize=5)
        self.thread = threading.Thread(target=self.capture_video)
        self.thread.daemon = True
        self.thread.start()

        # 初始时关闭摄像头
        self.camera_enabled = False
        # 不断读取摄像头画面并更新显示
        self.show_camera()

    def create_slider(self, parent, label, from_val, to_val, command, default_val):
        frame = tk.Frame(parent)
        frame.pack(fill=tk.X, pady=10, padx=10)

        label = tk.Label(frame, text=f"{label} ({from_val}-{to_val}):")
        label.pack(side=tk.LEFT)

        slider = tk.Scale(frame, from_=from_val, to=to_val, orient=tk.HORIZONTAL,command=command)
        slider.set(default_val)  # 设置初始值
        slider.pack(side=tk.LEFT, fill=tk.X, expand=True)

        return slider
    def reset_camera_defaults(self):
        # 设置所有摄像头参数为默认值
        self.set_exposure(self.default_exposure)
        self.set_exposure(-6)  # 曝光时间的默认值
        self.set_gain(110)  # 增益的默认值
        self.set_hue(0)  # 色调的默认值
        self.set_brightness(0)  # 亮度的默认值
        self.set_contrast(0)  # 对比度的默认值
        self.set_saturation(54)  # 饱和度的默认值
        self.set_gamma(124)  # Gamma的默认值
        self.set_sharpness(0)  # 锐度的默认值

        # 重置滑块位置
        self.exposure_scale.set(self.default_exposure)
        self.exposure_scale.set(-6)
        self.gain_scale.set(110)
        self.hue_scale.set(0)
        self.brightness_scale.set(0)
        self.contrast_scale.set(0)
        self.saturation_scale.set(54)
        self.gamma_scale.set(124)
        self.sharpness_scale.set(0)
    def refresh_qr_code_qr_info(self):
        if self.capture is not None:
            # 将图像转换为JPEG格式（从BGR转换为RGB）
            # ret, buffer = cv2.imencode('.jpg', cv2.cvtColor(self.capture, cv2.COLOR_BGR2RGB))
            ret = True
            if ret:
                height, width, channels = self.capture.shape
                image_base64 = base64.b64encode(self.capture).decode('utf-8')
                # 构建POST请求的数据
                data = {'image': image_base64,
                        'height': height,
                        'width': width,
                        'channels': channels
                        }
                # print(data)
                # print(buffer)
                # response = requests.post(self.url, json=data)
                try:
                    # # 发送POST请求，包含图像数据
                    # response = requests.post(self.url, json=data)
                    # if response.status_code == 200:
                        # POST请求成功后立即发送GET请求获取数据
                        response = requests.post(self.url, json=data)
                        if response.status_code == 200:
                            # 成功获取数据
                            data = response.json()
                            # 从响应中提取is_pass字段的值
                            qr_is_pass = data.get('qr_is_pass', False)  # 提供默认值以防字段不存在

                            # 检查qr_is_pass的值，根据它来更新界面
                            if qr_is_pass:
                                # 如果qr_is_pass为True，从响应中提取其他字段的值并更新文本框内容
                                qr_info = data.get('qr_info', '')  # 提供默认值以防字段不存在
                                time = data.get('time', '')  # 提取time字段的值
                                qr_points = data.get('qr_points', [])
                                box_points = data.get('box_points', [])

                                # 假设qr_points和box_points的数据格式是你所期望的
                                # 使用函数处理qr_points来获取qr_left和qr_bottom
                                points_positions = self.determine_qr_points_positions(qr_points)
                                qr_left = points_positions['left']
                                qr_bottom = points_positions['bottom']

                                if box_points:
                                    # 遍历box_points列表中的每个字典
                                    for box in box_points:
                                        bottom = box.get('bottom', '')
                                        left = box.get('left', '')
                                        right = box.get('right', '')
                                        top = box.get('top', '')

                                else:
                                    print("未找到box_points字段")

                                # 计算二维码所有x坐标和y坐标的平均值
                                qr_center_x = sum(point[0] for point in qr_points) / len(qr_points)
                                qr_center_y = sum(point[1] for point in qr_points) / len(qr_points)
                                # 计算物块所有x坐标和y坐标的平均值
                                center_x = (left[0] + right[0] + bottom[0] + top[0]) / 4
                                center_y = (left[1] + right[1] + top[1] + bottom[1]) / 4

                                # 像素偏移距离
                                x_offset_xs = qr_center_x - center_x
                                y_offset_xs = qr_center_y - center_y
                                k = 3.0659  # k为实际长度与像素长度的比值
                                # 实际偏移距离
                                self.x_offset_sj = x_offset_xs * k
                                self.y_offset_sj = y_offset_xs * k

                                # 根据偏移值的正负确定偏移方向和文本内容
                                x_direction = "向右" if self.x_offset_sj >= 0 else "向左"
                                y_direction = "向下" if self.y_offset_sj >= 0 else "向上"

                                # 将偏移值转换为正数
                                x_offset_abs = abs(self.x_offset_sj)
                                y_offset_abs = abs(self.y_offset_sj)

                                # 更新坐标点，并确保是整数元组形式
                                self.qr_center_point = (int(qr_center_x), int(qr_center_y))
                                self.point1 = (int(center_x), int(qr_center_y))
                                self.box_center_points = (int(center_x), int(center_y))
                                self.point2 = (int(qr_center_x), int(center_y))

                                # 绘制二维码红框
                                self.qr_points = np.array(data['qr_points'], np.int32).reshape((-1, 1, 2))
                                if self.capture is not None:
                                    # 绘制图形
                                    cv2.polylines(self.capture, [self.qr_points], isClosed=True,
                                                  color=(0, 0, 255), thickness=2)
                                    # 绘制矩形
                                    cv2.line(self.capture, self.qr_center_point, self.point1, (255, 0, 0),
                                             1)  # 蓝色竖线
                                    cv2.line(self.capture, self.point1, self.box_center_points, (0, 255, 0),
                                             1)  # 绿色横线
                                    cv2.line(self.capture, self.box_center_points, self.point2, (255, 0, 0),
                                             1)  # 蓝色竖线
                                    cv2.line(self.capture, self.point2, self.qr_center_point, (0, 255, 0),
                                             1)  # 绿色横线
                                    # 更新UI显示
                                    self.update_captured_image(self.capture)

                                # 更新坐标点
                                # 注意：这里需要根据实际情况来更新坐标点
                                # 计算角度和方向
                                angle, direction = self.calculate_angle_and_direction(qr_bottom, qr_left, bottom, left)

                                # 显示结果
                                result_str = f"角度：{angle:.2f}°, 方向：{direction}"

                                # 构建要显示的信息，包括qr_info和qr_time
                                info_text = f"二维码内容: {qr_info}\n偏移角度：{result_str}\nX偏移（蓝）：{x_direction}{x_offset_abs:.2f}mm，Y偏移（绿）：{y_direction}{y_offset_abs:.2f}mm\n最新检测时间: {time}"

                                self.qr_info_text.delete(1.0, "end")  # 清空Text组件
                                self.qr_info_text.insert("end", info_text)  # 插入新的内容
                            else:
                                # 如果qr_is_pass为False，显示错误信息
                                self.qr_info_text.delete(1.0, "end")  # 清空Text组件
                                self.qr_info_text.insert("end", "没有检测到二维码内容")  # 插入新的内容
                        else:
                            print(f"GET请求失败，状态码：{response.status_code}")

                except Exception as e:
                    print(f"请求过程中发生错误：{e}")
        else:
            print("没有处理过的帧可用于发送。")
    def update_captured_image(self, frame):
        # 绘制二维码的逻辑可以在这之前完成，比如在response中获取到二维码的位置信息后
        # 在frame上绘制出来，然后调用这个方法来更新UI
        photo = self.convert_image(frame)
        self.captured_photo_label.config(image=photo)
        self.captured_photo_label.image = photo  # 防止photo对象被垃圾回收

    def toggle_camera(self):
        # 切换摄像头状态
        self.camera_enabled = not self.camera_enabled
        print("摄像头状态:", "开启" if self.camera_enabled else "关闭")
        if self.camera_enabled:
            # 如果摄像头开启，则创建新的摄像头对象并启动捕获视频线程
            self.cap = cv2.VideoCapture(0)  # 重新初始化摄像头
            self.cap.set(cv2.CAP_PROP_FPS, 60)  # 重新设置帧率为60
            self.capture_button_frame.pack(fill=tk.X, padx=10, pady=5)
            if not self.thread.is_alive():
                self.capture_video_running = True  # 确保视频捕获循环可以运行
                self.thread = threading.Thread(target=self.capture_video)
                self.thread.daemon = True
                self.thread.start()
        else:
            # 如果摄像头关闭，则停止捕获视频线程并释放资源
            self.stop_capture_video()

    def stop_capture_video(self):
        # 设置捕获视频线程停止标志
        self.capture_video_running = False
        # 等待线程结束
        if self.thread.is_alive():
            self.thread.join()
        # 释放摄像头资源
        self.cap.release()

    def capture_video(self):
        # 设置捕获视频线程运行标志
        self.capture_video_running = True
        while self.capture_video_running:
            if self.camera_enabled:
                ret, frame = self.cap.read()
                if ret:
                    if not self.frame_queue.full():
                        self.frame_queue.put(frame)
            else:
                time.sleep(0.1)

    def show_camera(self):
        try:
            frame = self.frame_queue.get_nowait()
            self.processed_frame = frame
            photo = self.convert_image(frame)
            self.label.config(image=photo)
            self.label.image = photo
        except queue.Empty:
            pass
        self.window.after(10, self.show_camera)


    def convert_image(self, frame):

        # 将 OpenCV 图像转换为 Tkinter 支持的格式
        rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        img = Image.fromarray(rgb_image)
        photo = ImageTk.PhotoImage(image=img)
        return photo

    def set_exposure(self, value):
        # 曝光时间
        exposure_value = int(value)
        self.cap.set(cv2.CAP_PROP_EXPOSURE, exposure_value)

    def set_gain(self, value):
        # 设置增益
        gain_value = int(value)
        self.cap.set(cv2.CAP_PROP_GAIN, gain_value)

    def set_hue(self, value):
        hue_value = int(value)
        self.cap.set(cv2.CAP_PROP_HUE, hue_value)

    def set_brightness(self, value):
        # 设置亮度
        brightness_value = int(value)
        self.cap.set(cv2.CAP_PROP_BRIGHTNESS, brightness_value)

    def set_contrast(self, value):
        # 设置对比度
        contrast_value = int(value)
        self.cap.set(cv2.CAP_PROP_CONTRAST, contrast_value)

    def set_saturation(self, value):
        # 设置饱和度
        saturation_value = int(value)
        self.cap.set(cv2.CAP_PROP_SATURATION, saturation_value)

    # 在 CameraApp 类中添加以下方法
    def set_gamma(self, value):
        gamma_value = float(value)
        self.cap.set(cv2.CAP_PROP_GAMMA, gamma_value)

    def set_sharpness(self, value):
        # 设置锐度
        sharpness_value = int(value)
        self.cap.set(cv2.CAP_PROP_SHARPNESS, sharpness_value)

    def capture_image(self):
        # 只拍摄图像并显示，不直接保存
        if self.camera_enabled:
            ret, frame = self.cap.read()
            if ret:
                photo = self.convert_image(frame)
                self.captured_photo_label.config(image=photo)
                self.captured_photo_label.image = photo  # 防止photo对象被垃圾回收
                self.capture = frame    #将拍摄到的那一帧图像保存下来
                # 拍摄后显示检测二维码按钮、保存图片按钮和文本框
                self.btn_refresh_qr_info.pack(side=tk.LEFT, padx=5)
                self.btn_save_image.pack(side=tk.LEFT, padx=5)
                self.qr_info_text.pack(pady=5)

            else:
                print("拍摄失败。")

    def save_image(self):
        if self.capture is not None:
            # 检查保存目录是否存在，如果不存在则创建
            save_directory = "pictures"
            if not os.path.exists(save_directory):
                os.makedirs(save_directory)

            # 生成唯一的文件名
            unique_filename = str(uuid.uuid4()) + ".jpg"

            # 拼接文件路径
            save_path = os.path.join(save_directory, unique_filename)

            # 保存绘制后的图像
            cv2.imwrite(save_path, self.capture)
            print(f"图像已保存到: {save_path}")
        else:
            print("没有可保存的处理后图像。")

    def quit(self):
        # 释放摄像头资源并关闭窗口
        self.cap.release()
        self.window.destroy()
def start_camera_app():
    root = tk.Tk()
    app = CameraApp(root, "调试软件")
    root.mainloop()

if __name__ == "__main__":
    threading.Thread(target=start_camera_app).start()